home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Internet Tools 1993 July / Internet Tools.iso / RockRidge / mail / mm / ccmd / ccmdut.c < prev    next >
Encoding:
C/C++ Source or Header  |  1990-12-18  |  19.7 KB  |  722 lines

  1. /*
  2.  Copyright (c) 1986, 1990 by The Trustees of Columbia University in
  3.  the City of New York.  Permission is granted to any individual or
  4.  institution to use, copy, or redistribute this software so long as it
  5.  is not sold for profit, provided this copyright notice is retained.
  6.  
  7.  Author: Andrew Lowry
  8. */
  9. /* ccmdut
  10. **
  11. ** Utility routines for use by action routines, function handlers,
  12. ** etc.
  13. **/
  14.  
  15. #include "ccmdlib.h"            /* get ccmd package symbols */
  16. #include "cmfncs.h"            /* and internal symbols */
  17.  
  18. /* break table that breaks on everything, shared by some of the parse
  19. ** functions.
  20. **/
  21.  
  22. brktab cmallbk = {
  23.   {
  24.     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
  25.     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
  26.   },
  27.   {
  28.     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
  29.     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
  30.   }
  31. };
  32.         
  33.  
  34.  
  35. /* cmstin, cmsti1, cmsti  -- simulate terminal input
  36. **
  37. ** Purpose:
  38. **   Stuffs characters into the command line buffer, optionally
  39. **   echoing them as they are stuffed.  Use the three routines
  40. **   as follows:
  41. **
  42. **     ret = cmstin(s,n,flags); -- stuff n characters from s
  43. **     ret = cmsti1(c,flags);   -- stuff single character c
  44. **     ret = cmsti(s,flags);    -- stuff null-terminated string s
  45. **
  46. **   Characters are combined with the flags value before stuffing
  47. **   into the command line.  If the CC_NEC flag is on, no echoing
  48. **   occurs.
  49. **
  50. ** Input arguments:
  51. **   c - The character to be stuffed (cmsti1 only).
  52. **   s - A pointer to the string to be stuffed (cmstin and cmsti only).
  53. **   n - The number of characters to be stuffed (cmstin only).  -1 means
  54. **     stuff until a null character is encountered.
  55. **   flags - Flag bits to be set in the left half of the (int) entry
  56. **     for the character(s).  The right half of flags should be zero.
  57. **
  58. ** Output arguments: None.
  59. ** Returns: Standard error code
  60. **/
  61.  
  62. int
  63. cmstin(s,n,flags)
  64. char *s;
  65. int n, flags;
  66. {
  67.   char c;            /* individual chars to fill */
  68.   int mod = FALSE;
  69.   
  70.   if ((s == NULL) || (n == 0))    /* nothing to stuff? */
  71.     return(CMxOK);        /* ok by me! */
  72.  
  73.   if (cmcsb._cmflg & CM_NEC)
  74.     flags |= CC_NEC;        /* set noecho flag if source not echoable */
  75.  
  76.   if (flags & CC_HID)
  77.     flags |= CC_NEC;        /* hidden implies not echoed */
  78.  
  79.   if (flags & CC_ACT)
  80.     flags |= CC_ACT;
  81.   cmcsb._cmflg |= CM_DRT;    /* buffer is now dirty */    
  82.  
  83.   if (cmcsb._cmcur != cmcsb._cmptr + cmcsb._cminc) {
  84.       cmceol();            /* clear to eol */
  85.       mod = TRUE;
  86.   }
  87.   while (n-- != 0) {        /* loop through to end of string */
  88.     if ((n < 0) && (*s == NULCHAR)) /* check for null termination */
  89.       return(CMxOK);
  90.     if (cmcsb._cmcnt == 0)    /* watch for buffer overflow */
  91.       return(CMxBOVF);
  92.     c = *s++;            /* get next char to stuff */
  93.     if ((flags & CC_NEC) == 0) {
  94.       cmechx(c);        /* echo chars if needed */
  95.       mod = TRUE;
  96.     }
  97.     if ((cmcsb._cmflg & CM_RAI) && /* raising input? */
  98.     ((flags & CC_QUO) == 0) /* and not quoting this char? */
  99.        )
  100.       if ((c >= 'a') && (c <= 'z')) /* and this char is lowercase? */
  101.     c -= 'a'-'A';        /* yup, convert to uppercase */
  102.     
  103.  
  104.     bcopy(cmcsb._cmcur, cmcsb._cmcur + 1,
  105.       (cmcsb._cminc + cmcsb._cmptr - cmcsb._cmcur)*sizeof(*cmcsb._cmbfp));
  106.     cmcsb._cminc++;
  107.     *(cmcsb._cmcur++) = c | flags; /* insert and count the char */
  108.     cmcsb._cmcnt--;        /* and adjust space remaining */
  109.   }
  110.   if (mod)
  111.       refresh_eol();        /* display the rest of the line */
  112.   return(CMxOK);        /* all done */
  113. }
  114.  
  115. int
  116. cmsti1(c,flags)
  117. char c;
  118. int flags;
  119. {
  120.   return(cmstin(&c,1,flags)); /* stuff one character */
  121. }
  122.  
  123.  
  124. int
  125. cmsti(s,flags)
  126. char *s;
  127. int flags;
  128. {
  129.   return(cmstin(s,-1,flags));    /* -1 means stuff til null char */
  130. }
  131.  
  132.  
  133.  
  134. /* echo
  135. **
  136. ** Purpose:
  137. **   Print character representations on the source terminal.  Control
  138. **   characters are printed as in ^A for control-A.  Delete prints as
  139. **   ^?.  Linefeed echoes as a newline sequence.  Tabs are expanded
  140. **   into equivalent spaces.  If the column position exceeds _cmcmx in the
  141. **   CSB a newline is generated before echoing the character.
  142. **
  143. ** Input arguments:
  144. **   c - The character to be echoed.
  145. **
  146. ** Output arguments: None
  147. ** Returns: Nothing.
  148. **/
  149.  
  150. cmechx(c)
  151. char c;
  152. {
  153.   int cpos;            /* column counter */
  154.  
  155.   c &= 0x7f;
  156.  
  157.   if (c == NEWLINE)        /* newline */
  158.     cmxnl();
  159.   else if (c == TAB)        /* tab */
  160.     do {
  161.       cmechx(SPACE);
  162.     } while ((cmcsb._cmcol != 0) && ((cmcsb._cmcol % 8) != 0));
  163.                     /* space to newline or a multiple of 8 */
  164.   else if ((c == DELETE) || (c < SPACE)) { /* other control char? */
  165.     cmechx('^');        /* print up-arrow */
  166.     cmechx((char) (c ^ 0x40));
  167.   }
  168.   else {            /* normal character */
  169.     cmxputc(c);            /* just print it */
  170.   }
  171.   /*
  172.    * XXX This wraps one column too early.  This sidesteps the "am && !xn"
  173.    * problem, but it's not consistent with other code (that does step into
  174.    * the problem).  Should we always avoid the last column?  Otherwise
  175.    * cmnl() should look at the _cmcol, am, and xn.  -- chris
  176.    */
  177.   if ((cmcsb._cmcol >= cmcsb._cmcmx) && (c != NEWLINE))
  178.     cmxnl();
  179. }
  180.  
  181.  
  182.  
  183. /* cmhelp
  184. **
  185. ** Purpose:
  186. **   Steps through a chain of FDB's and prints a help message for
  187. **   each one.  If the user supplied a help string, it is output
  188. **   first.  Then, if the CM_SDH flag is off in the FDB, the function's
  189. **   help routine is invoked to print the standard help message for
  190. **   the field.  A flag is passed to the standard help routine indicating
  191. **   whether or not a custom help string was printed.
  192. **
  193. ** Input arguments:
  194. **   fdblist - A pointer to the first FDB in the chain of alternates.
  195. **   helpchar - The character that invoked the help action (normally '?'),
  196. **     to be echoed before the help message.  Pass NULCHAR ('\0') to
  197. **     suppress this.
  198. **
  199. ** Output arguments: None.
  200. ** Returns: Standard return code.
  201. **/
  202.  
  203. #define CHECK_HELP() \
  204.   if (helplines >= cmcsb._cmrmx) { \
  205.     if (!cmhelp_more("--space to continue, Q to stop--")) { \
  206.       helplines = -1; \
  207.       break; \
  208.     } \
  209.     else helplines = 0; \
  210.   }
  211.  
  212. int
  213. cmhelp(fdblist,helpchar)
  214. fdb *fdblist;
  215. char helpchar;
  216. {
  217.   int firsthelp = TRUE;        /* guides whether to print "or" */
  218.   int cust;            /* TRUE if custom help given for an FDB */
  219.   int ret;            /* return code from handlers */
  220.   int inputlen;            /* count of input available */
  221.   ftspec *ft;            /* function handler for fdb */
  222.   int *cp, *cpmax;        /* for scanning buffer during refresh */
  223.   int helplines = 0;
  224. #ifdef undef
  225.   if ((cmcsb._cmflg & CM_TTY) == 0)
  226.     return(CMxOK);        /* no help to non terminals */
  227. #endif
  228.   
  229.                 /* remove hyphen-newline combinations */
  230.   ret = cmprep(cmcsb._cmwbp,cmcsb._cmwbc,&inputlen);
  231.   if (ret != CMxOK)
  232.     return(ret);        /* propagate errors */
  233.   if (helpchar != NULCHAR) {
  234.     cmechx(helpchar);        /* this is generally a question mark */
  235.     cmechx(SPACE);        /* and separate from help text */
  236.   }
  237.   while (fdblist != NULL && helplines >= 0) {
  238.     cust = (fdblist->_cmhlp != NULL); /* see if they gave a help string */
  239.     if (cust || ((fdblist->_cmffl & CM_SDH) == 0)) /* any help at all? */
  240.       if (firsthelp) {
  241.     firsthelp = FALSE;    /* yes, future FDB's won't be first msg */
  242.     helplines = 1;        /* first screen is one line shorter */
  243.                 /* so that they can see the line they typed */
  244.       }
  245.       else {
  246.     if (fdblist->_cmffl & CM_NLH) {
  247.       CHECK_HELP();
  248.       cmxputc('\n');
  249.       helplines++;
  250.       CHECK_HELP();
  251.     }
  252.     cmxputs("  or ");    /* start alternative when not the first */
  253.       }
  254.     if (cust) {
  255.       cmxputs(fdblist->_cmhlp);    /* print custom help if any */
  256.       helplines += count_nl(fdblist->_cmhlp); /* count the lines it takes */
  257.     }
  258.  
  259.     if ((fdblist->_cmffl & CM_SDH) == 0) { /* std help if not suppressed */
  260.       ft = cmfntb[fdblist->_cmfnc-1]; /* get the function handler */
  261.                       /* and invoke the help handler */
  262.       ret = (*ft->_fthlp)(cmcsb._cmwbp,inputlen,fdblist,cust,
  263.               cmcsb._cmrmx-helplines); 
  264.       if (ret != -1)
  265.       helplines = cmcsb._cmrmx - ret; /* fix up number of lines so far */
  266.       else
  267.       helplines = -1;
  268.     }
  269.  
  270.     if (cust || ((fdblist->_cmffl & CM_SDH) == 0)) {
  271.       cmxnl();            /* if any help given, finish with newline */
  272.       CHECK_HELP();
  273.     }
  274.     if (cust && ((fdblist->_cmffl & CM_SDH))) {
  275.       CHECK_HELP();
  276.       helplines++;
  277.     }
  278.     CHECK_HELP();
  279.     fdblist = fdblist->_cmlst;    /* now move on to next choice */
  280.   }
  281.   cmxputs(cmcsb._cmrty);    /* now reprint prompt */
  282.   cpmax = cmcsb._cmptr + cmcsb._cminc; /* this is as far as refresh goes */
  283.   for (cp = cmcsb._cmbfp; cp != cpmax; cp++) /* loop through buffered input */
  284.     if ((*cp & CC_NEC) == 0)    /* originally echoed? */
  285.       cmechx((char) *cp & CC_CHR); /* yup, echo it again */
  286.   
  287.   go_from(cpmax,cmcsb._cmcur);
  288.  
  289.   return(CMxOK);        /* Fine! */
  290. }
  291.  
  292. static
  293. count_nl(str) 
  294. char *str;
  295. {
  296.     int count = 0;
  297.     char *cp=str, *bp, *index();
  298.  
  299.     while (1)
  300.     if ((bp = index(cp, '\n')) != NULL) {
  301.         count++;
  302.         cp = bp + 1;
  303.     }
  304.     else
  305.         break;
  306.     return(count);
  307. }
  308.  
  309.  
  310. /* cmcplt
  311. **
  312. ** Purpose:
  313. **   Attempt to get completion for the current parse field.  Field
  314. **   _cmifd of the CSB must be pointing to an FDB which produced an
  315. **   incomplete parse on the current input.  The completion handler
  316. **   for that FDB is invoked to provide completion text, which is
  317. **   stuffed into the command buffer with echoing.  Either CMxOK
  318. **   or CMxGO is returned, depending on whether or not the completion
  319. **   handler requested wakeup.  Any completion that asks for wakeup
  320. **   causes flag CM_PFE to be turned on in the CSB, to activate following
  321. **   noise word fields.
  322. **
  323. ** Input arguments:
  324. **   full - If TRUE, full completion will be requested.  Otherwise,
  325. **     partial completion will be requested.
  326. **
  327. ** Output arguments: None.
  328. ** Returns: CMxGO for wakeup, or CMxOK for no wakeup.
  329. **/
  330.  
  331. int
  332. cmcplt(full)
  333. int full;
  334. {
  335.   int ret;            /* return code from aux routines */
  336.   int flags;            /* flags returned by completion handler */
  337.   int (*cmp)();            /* function completion handler */
  338.   int inputlen;            /* input count available */
  339.   char *ctext;            /* pointer to returned completion text */
  340.   int ctlen;            /* number of characters in completion text */
  341.   int i,j;
  342.   int same=FALSE;
  343.  
  344.   ret = cmprep(cmcsb._cmwbp,cmcsb._cmwbc,&inputlen); /* clean up input */
  345.   if (ret != CMxOK)
  346.     return(ret);        /* propagate errors */
  347.  
  348.   cmp = cmfntb[cmcsb._cmifd->_cmfnc-1]->_ftcmp; /* get the handler */
  349.                 /* and invoke it */
  350.   flags = (*cmp)(cmcsb._cmwbp,inputlen,cmcsb._cmifd,full,&ctext,&ctlen);
  351.   if (ctext != NULL) {
  352.     if (flags & CMP_PNC) {     /* stop after punctuation? */
  353.       for (i = 0; (i < ctlen) || (ctlen == -1); i++) /* scan text */
  354.         if ((ctlen == -1) && (ctext[i] == NULCHAR))
  355.         break;        /* stop at end of null-terminated string */
  356.         else if (!isalnum(ctext[i])) {    /* XXX */
  357.       i++;            /* count a punctuation character */
  358.       break;        /* and stop scanning */
  359.         }
  360.       ctlen = i;        /* only stuff this much */
  361.     }
  362.  
  363.     if (ctlen == -1)
  364.       ctlen = strlen(ctext);
  365.     if (ctlen > cmcsb._cmptr + cmcsb._cminc - cmcsb._cmcur)
  366.       same = FALSE;
  367.     else {
  368.       for(same = TRUE, j = 0; j < ctlen; j++) {
  369.     if (ctext[j] != (cmcsb._cmcur[j] & CC_CHR)) {
  370.       same = FALSE;
  371.       break;
  372.     }
  373.       }
  374.     }
  375.     if (same) {
  376.       go_forward_char(ctlen);
  377.       cmcsb._cmcur += ctlen;
  378.       ret = CMxOK;
  379.     }
  380.     else
  381.       ret = cmstin(ctext,ctlen,0); /* stuff the supplied completion text */
  382.     cmxflsh();
  383.   }
  384.   if (ret != CMxOK)
  385.       return(ret);        /* propagate problem */
  386.   if (flags & CMP_SPC) {
  387.     if (cmcsb._cmcur < cmcsb._cmptr + cmcsb._cminc &&
  388.     (*cmcsb._cmcur & CC_CHR) == SPACE) {
  389.     go_forward_char(1);
  390.     cmcsb._cmcur++;
  391.     ret = CMxOK;
  392.     }
  393.     else {
  394.     ret = cmsti1(SPACE,0);    /* and trailing space if requested */
  395.     }
  396.     cmxflsh();
  397.     if (ret != CMxOK)
  398.       return(ret);        /* propagate problem */
  399.   }
  400.   if (flags & CMP_BEL) {
  401.     cmputc(BELL,cmcsb._cmoj);        /* beep if they asked us to */
  402.     cmxflsh();
  403.   }
  404.   if (flags & CMP_GO) {
  405.     cmcsb._cmflg |= CM_PFE;    /* wants wakeup - activate noise words */
  406.     return(CMxGO);        /* return success with wakeup */
  407.   }
  408.   else
  409.     return(CMxOK);        /* or without */
  410. }
  411.  
  412.  
  413.  
  414. /* cmdflt
  415. **
  416. ** Purpose:
  417. **   Fill in a default string if appropriate.  If the command buffer
  418. **   is empty, and if any of the FDB's for the current parse specifies
  419. **   a default string, it is stuffed into the buffer and echoed.
  420. **
  421. ** Input arguments:
  422. **   fdblist - A pointer to the chain of FDB's.
  423. **
  424. ** Output arguments: None.
  425. ** Returns: CMxOK if default successfully stuffed, other standard return
  426. **   code otherwise.
  427. **/
  428.  
  429. int
  430. cmdflt(fdblist)
  431. fdb *fdblist;
  432. {
  433.   char *dflt;                /* string to stuff */
  434.   int ret;
  435.  
  436.   if (cmcsb._cminc == 0) {
  437.     while (fdblist != NULL)        /* step through FDB's */
  438.       if ((dflt = fdblist->_cmdef) != NULL)
  439.     break;                /* until a default is found */
  440.       else
  441.     fdblist = fdblist->_cmlst;    /* move to next FDB */
  442.     if (dflt != NULL) {
  443.       ret = cmsti(dflt,0);        /* then stuff it with echoing */
  444.       if (ret == CMxOK)
  445.     ret == cmsti1(SPACE,0);        /* followed by a space */
  446.       return(ret);
  447.     }
  448.   }
  449.   return(CMxNDEF);            /* nothing stuffed */
  450. }
  451.  
  452. /*
  453.  * CMPDFLT:
  454.  * fill in default for partial matches
  455.  * only stuff the string up to a punctuation...
  456.  */
  457.  
  458. int
  459. cmpdflt(fdblist)
  460. fdb *fdblist;
  461. {
  462.   char *dflt;                /* string to stuff */
  463.   int ret;
  464.   int i;
  465.  
  466.   if (cmcsb._cminc == 0) {
  467.     while (fdblist != NULL)        /* step through FDB's */
  468.       if ((dflt = fdblist->_cmdef) != NULL)
  469.     break;                /* until a default is found */
  470.       else
  471.     fdblist = fdblist->_cmlst;    /* move to next FDB */
  472.     if (dflt != NULL) {
  473.       for( i = 0; i < strlen(dflt) ; i++) {
  474.     if (!isalnum(dflt[i])) break;    /* XXX */
  475.     ret = cmsti1(dflt[i],0);
  476.     if (ret != CMxOK)
  477.       break;
  478.       }
  479.       return(ret);
  480.     }
  481.   }
  482.   return(CMxNDEF);            /* nothing stuffed */
  483. }
  484.  
  485.  
  486. /* cmprep
  487. **
  488. ** Purpose:
  489. **   Copy unparsed input text with skipped characters removed
  490. **   into another buffer, without the high order flag bytes.
  491. **   As a side effect, any ineligible conditional skip characters
  492. **   have their CC_CSK flags cleared.
  493. **
  494. ** Input arguments:
  495. **   tobuf - Pointer to buffer to hold prepared text.
  496. **   tosize - Size of destination buffer.
  497. **
  498. ** Output arguments:
  499. **   tolen - Number of characters in prepared text.
  500. **
  501. ** Returns: Standard return code.
  502. **/
  503.  
  504. int
  505. cmprep(tobuf,tosize,tolen)
  506. char *tobuf;
  507. int tosize, *tolen;
  508. {
  509.   char c;            /* individual chars from destination */
  510.   int cc;
  511.   int *frombuf;            /* pointer to source buffer */
  512.   int fromlen;            /* number of chars to copy from source */
  513.   int cskip = 0;        /* number of conditional skips in a run */
  514.   int *cskippos;        /* position of start of run */
  515.  
  516.   frombuf = cmcsb._cmptr;    /* point to text to be copied */
  517.   fromlen =  cmcsb._cmcur - frombuf; /* number of chars to copy */
  518.   *tolen = 0;            /* no chars copied yet */
  519.   while (fromlen-- > 0)    {    /* loop over source */
  520.     c = (cc = *frombuf++) & CC_QCH; /* get next source character */
  521.     if (cc & CC_SKP) {        /* skip this character? */
  522.       cskip = 0;        /* yes, and last run of conditional skips */
  523.     }
  524.     else if (cc & CC_CSK) {    /* conditionally skip character? */
  525.       if (cskip++ == 0)        /* count and check for start of run */
  526.     cskippos = frombuf-1;    /* start of run -- remember position */
  527.     }
  528.     else {            /* no type of skip */
  529.       while (cskip-- > 0) {    /* maybe we ended a run of ineligible skips */
  530.         if (*tolen >= tosize)    /* copy each char */
  531.           return(CMxIOVF);    /* no room */
  532.     *tobuf++ = *cskippos & CC_QCH;
  533.     (*tolen)++;        /* and count it */
  534.     *cskippos++ &= ~CC_CSK; /* and turn off conditional skip flag */
  535.       }
  536.       cskip = 0;        /* (above loop leaves cskip == -1) */
  537.       if (*tolen >= tosize)    /* room for current char? */
  538.     return(CMxIOVF);    /* nope */
  539.       *tobuf++ = c;        /* copy it */
  540.       (*tolen)++;        /* and count it */
  541.     }
  542.   }
  543.   while (cskip-- > 0) {        /* in case we finished with a run */
  544.     if (*tolen >= tosize)    /* copy each char */
  545.       return(CMxIOVF);        /* no room */
  546.     *tobuf++ = *cskippos & CC_CHR;
  547.     (*tolen)++;            /* and count it */
  548.     *cskippos++ &= ~CC_CSK;    /* and turn off conditional skip flag */
  549.   }
  550.   return(CMxOK);
  551. }
  552.  
  553.  
  554.  
  555. /* cmperr, cmgerr
  556. **
  557. ** Purpose:
  558. **   Print or return a pointer to an error message corresponding to a
  559. **   given ccmd return code.  Cmperr prints the message, starting at
  560. **   the left edge of a new line, and prefixed with a question mark.
  561. **   Cmgerr returns a pointer to the message string, without a question
  562. **   mark.  In addition, cmperr flushes any typeahead that has accumulated
  563. **   at the input source.
  564. **
  565. ** Input arguments:
  566. **   ecode - The return code to be interpreted.
  567. **
  568. ** Output arguments: None.
  569. ** Returns: Nothing.
  570. **/
  571.  
  572. char *
  573. cmgerr(ecode)
  574. int ecode;
  575. {
  576.   int lh,rh;            /* pieces of decomposed code */
  577.   static char unkerr[] =    /* template for return string for bad code */
  578.     "Unknown command parsing error: xxx, xxx ";
  579.  
  580.   lh = ecode >> 8;        /* decompose error code */
  581.   rh = ecode & 0xff;
  582.   
  583.   if ((lh > cmfmax) ||        /* function code out of range? */
  584.       (rh >= fnetab[lh]->_fecnt) /* or error code too high for function? */
  585.      ) {
  586.     sprintf(unkerr,"Unknown command parsing error: %3d, %3d",lh,rh);
  587.     return(unkerr);        /* return catch-all message */
  588.   }
  589.   else
  590.     return(fnetab[lh]->_ferrs[rh]); /* else return selected message */
  591. }
  592.  
  593. cmperr(ecode,flags)
  594. int ecode;
  595. {
  596.   char *estr;
  597.  
  598. #ifdef undef
  599.   if ((cmcsb._cmflg & CM_TTY) == 0)
  600.     return;            /* no output to nonterminal */
  601. #endif
  602.   estr = cmgerr(ecode);        /* get the error code */
  603.   cmflush(cmcsb._cmij);        /* flush waiting input */
  604.   if (cmcpos() != 0)
  605.     cmnl(cmcsb._cmej);        /* get to beginning of line */
  606.   cmcsb._cmcol = 0;        /* make sure our counter agrees */
  607.   cmputc('?',cmcsb._cmej);    /* start with question mark */
  608.   cmputs(estr,cmcsb._cmej);    /* then the error string */
  609.   if (cmcsb._cmcnt > 0 && !(flags & CM_SDE)) {
  610.     int i = 0;
  611.     int empty = TRUE;        /* may not be anything to print */
  612.     char c = cmcsb._cmptr[0];
  613.     while (i < cmcsb._cminc && isascii(c) && isprint(c)) {
  614.       if (empty)        /* found something, print separator */
  615.     cmputs(" - \"", cmcsb._cmej), empty = FALSE;
  616.       cmputc(c, cmcsb._cmej);
  617.       c = cmcsb._cmptr[++i];
  618.     }
  619.     if (!empty)
  620.       cmputc('"', cmcsb._cmej);
  621.   }      
  622.   cmnl(cmcsb._cmej);        /* tie off with newline */
  623. }
  624.  
  625.  
  626. cmpemsg(estr,flags)
  627. char *estr;
  628. int flags;
  629. {
  630.   cmflush(cmcsb._cmij);        /* flush waiting input */
  631.   if (cmcpos() != 0)
  632.     cmnl(cmcsb._cmej);        /* get to beginning of line */
  633.   cmcsb._cmcol = 0;        /* make sure our counter agrees */
  634.   cmputc('?',cmcsb._cmej);    /* start with question mark */
  635.   cmputs(estr,cmcsb._cmej);    /* then the error string */
  636.   if (cmcsb._cmcnt > 0 && !(flags & CM_SDE)) {
  637.     int i = 0;
  638.     int empty = TRUE;        /* may not be anything to print */
  639.     char c = cmcsb._cmptr[0];
  640.     while (i < cmcsb._cminc && isascii(c) && isprint(c)) {
  641.       if (empty)        /* found something, print separator */
  642.     cmputs(" - \"", cmcsb._cmej), empty = FALSE;
  643.       cmputc(c, cmcsb._cmej);
  644.       c = cmcsb._cmptr[++i];
  645.     }
  646.     if (!empty)
  647.       cmputc('"', cmcsb._cmej);
  648.   }      
  649.   cmnl(cmcsb._cmej);        /* tie off with newline */
  650. }
  651.  
  652.  
  653. /* fdbchn
  654. **
  655. ** Purpose:
  656. **   Chain together a list of FDB's, and return a pointer to
  657. **   the first FDB in the chain.
  658. **
  659. ** Input arguments:
  660. **   fdbs - A comma-separated list of pointers to FDB's to be 
  661. **     chained together, terminated by a NULL pointer.
  662. **
  663. ** Output arguments: None.
  664. ** Returns: Pointer to the head of the FDB chain.
  665. **/
  666.  
  667. fdb *
  668. fdbchn(va_alist)
  669. va_dcl
  670. {
  671.   va_list fdbs;
  672.   fdb *head, *fdbptr;
  673.  
  674.   va_start(fdbs);
  675.  
  676.   head = fdbptr = (fdb *) va_arg(fdbs, fdb *);
  677.  
  678.   while(fdbptr)
  679.     fdbptr = fdbptr->_cmlst = (fdb *) va_arg(fdbs, fdb *);
  680.  
  681.   va_end(fdbs);
  682.  
  683.   return(head);            /* all linked */
  684. }
  685.  
  686. /*
  687.  * prompt with string.  returns whether or not to continue outputting.
  688.  * returns TRUE or FLASE to continue or not
  689.  */
  690. cmhelp_more(str)
  691. char *str;
  692. {
  693.     int c;
  694.     if (cmcsb._cmflg2 & CM_NHM)
  695.     return(TRUE);
  696.  
  697.     cmxprintf("%s", str);
  698.     c = getchar();
  699.     switch(c) {
  700.     case 'Y':
  701.     case 'y':
  702.     case ' ':
  703.     cmxcll();            /* clear current line */
  704.     return(TRUE);
  705.     default:
  706.     cmechx(c);
  707.     if (c != '\n') cmechx('\n');
  708.     return(FALSE);
  709.     }
  710. }
  711.  
  712. /*
  713.  * interpret CCMD environment variable
  714.  */
  715. cmgetenv()
  716. {
  717.     char *getenv(), *env = getenv("CCMDOPT");
  718.     if (env) {
  719.     cm_env_actions(env);
  720.     }
  721. }
  722.